//
//  NSObject+Model.m
//  justTest
//
//  Created by 小零钱 on 2018/3/2.
//  Copyright © 2018年 小零钱. All rights reserved.
//


/*
 字典转模型Runtime实现
 思路：利用运行时，遍历模型中所有属性，根据模型的属性名，去字典中查找key，取出对应的值，给模型的属性赋值（从提醒：字典中取值，不一定要全部取出来）。
 考虑情况：
    1.当字典的key和模型的属性匹配不上。
    2.模型中嵌套模型（模型属性是另外一个模型对象）。
    3.数组中装着模型（模型的属性是一个数组，数组中是一个个模型对象）。
 注解：根据上面的三种特殊情况，先是字典的key和模型的属性不对应的情况。不对应有两种，一种是字典的键值大于模型属性数量，这时候我们不需要任何处理，因为runtime是先遍历模型所有属性，再去字典中根据属性名找对应值进行赋值，多余的键值对也当然不会去看了；另外一种是模型属性数量大于字典的键值对，这时候由于属性没有对应值会被赋值为nil，就会导致crash，我们只需加一个判断即可。
 */

#import "NSObject+Model.h"
#import <objc/message.h>

@implementation NSObject (Model)

#pragma mark - 1、字典转模型-->字典的 key 和模型的属性不匹配
// runtime 字典转模型-->字典的 key 和模型的属性不匹配「模型属性数量大于字典键值对数」，这种情况处理如下：
// Runtime:根据模型中属性,去字典中取出对应的value给模型属性赋值
// 思路：遍历模型中所有属性->使用运行时
+ (instancetype)modelWithDict:(NSDictionary *)dict {
    // 1、创建对应的对象
    id obj = [[self alloc] init];
    
    // 2、利用runtime给对象中的属性赋值
    /*
     class_copyIvarList：获取类的所有成员变量
     Ivar：成员变量
     第一个参数：表示获取哪个类的成员变量
     第二个参数：表示这个类有多少成员变量，传入一个无符变量地址，会自动给这个变量赋值
     返回值Ivar *：指的是一个ivar数组，会把所有成员属性放在一个数组中，通过返回的数组就能全部获取到
     count：成员变量个数
    */
    unsigned int count = 0;
    Ivar * ivars = class_copyIvarList(self, &count);
    for (int i = 0; i < count; i++) {
        //根据角标，从数组取出对应的成员变量
        Ivar ivar = ivars[i];
        
        //获取成员变量名字
        NSString * ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        
        //处理成员变量名 -> 字典中的key(去掉_，从第一个角标开始截取)
        NSString * key = [ivarName substringFromIndex:1];
        
        //根据成员属性名去字典中查找对应的value
        id value = dict[key];
        
        //如果模型属性数量大于字典键值对数量，模型属性会被赋值为nil，从而报错(could not set nil as the value for the key age.)
        if (value) {
            //给模型赋值
            [obj setObject:value forKey:key];
        }
    }
    return obj;
}



#pragma mark - 2、字典转模型-->模型中嵌套模型「模型属性是另外一个模型对象」
+ (instancetype)modelWithDict2:(NSDictionary *)dict {
    // 1、创建对应的对象
    id objc = [[self alloc] init];
    
    // 2、利用runtime给对象中的属性赋值
    unsigned int count = 0;
    //获取类中所有成员变量
    Ivar * ivarList = class_copyIvarList(self, &count);
    
    //遍历所有成员变量
    for (int i = 0; i < count; i++) {
        //根据角标，从数组取出对应的成员变量
        Ivar ivar = ivarList[i];
        
        //获取成员变量名字
        NSString * ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        //获取成员变量类型
        NSString * ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
        
        //替换：@\"User"\ -> User
        ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
        ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
        
        //处理成员属性名->字典中的key（去掉_，从第一个角标开始截取）
        NSString * key = [ivarName substringFromIndex:1];
        
        //根据成员属性名去字典中查找对应的value
        id value = dict[key];
        
        /*******************************  二级转换 ******************************/
        //如果字典中还有字典，也需要把对应的字典转换成模型。判断下value是否为字典，并且是自定义对象才需要转换
        if ([value isKindOfClass:NSDictionary.class] && ![value hasPrefix:@"NS"]) {
            //字典转模型 userDict => User模型，转换成哪个模型
            //根据字符串类名生成对象
            Class modelClass = NSClassFromString(ivarType);
            
            //有对应的模型才需要转换
            if (modelClass) {
                //把字典转成模型
                value = [modelClass modelWithDict2:value];
            }
        }
        
        // 给模型中属性赋值
        if (value) {
            [objc setObject:value forKey:key];
        }
    }
    return objc;
}



#pragma mark - 3、字典转模型-->数组中装着模型「模型的属性是一个数组，数组中是字典模型对象」
// Runtime：根据模型中属性，去字典中取出对应的value给模型属性赋值
// 思路：遍历模型中所有属性 -> 使用运行时
//+ (instancetype)modelWithDict3:(NSDictionary *)dict {
//    // 1、创建对应的对象
//    id objc = [[self alloc] init];
//
//    // 2、利用runtime给对象中属性赋值
//    unsigned int count = 0;
//    //获取类在中的所有成员变量
//    Ivar * ivarList = class_copyIvarList(self, &count);
//
//    // 遍历所有变量
//    for (int i = 0; i < count; i++) {
//        //根据角标，从数组中取出对应的成员变量
//        Ivar ivar = ivarList[i];
//
//        //获取成员变量名字
//        NSString * ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
//
//        //处理成员属性名 -> 字典中的key（去掉_，从第一个角标开始截取）
//        NSString * key = [ivarName substringFromIndex:1];
//
//        //根据成员属性名去字典中查找对应的value
//        id value = dict[key];
//
//        /*******************************  三级转换 ******************************/
//        //三级转换：NSArray中也是字典，把数组中的字典转换成模型
//        //判断是否是数组
//        if ([value isKindOfClass:NSArray.class]) {
//            //判断对应类有没有实现字典数组转模型数组的协议
//            //arrayContainModelClass 提供一个协议，只要遵守这个协议的类，都能把数组中的字典转模型
//            if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
//
//                //转换成id类型，就能调用任何对象方法
//                id idSelf = self;
//
//                //获取数组中字典对应的模型
//                NSString * type = [idSelf arrayContainModelClass][key];
//
//                //生成模型
//                Class classModel = NSClassFromString(type);
//                NSMutableArray * arrM = [NSMutableArray array];
//                //遍历字典数组，生成模型数组
//                for (NSDictionary * dic in value) {
//                    //字典转模型
//                    id model = [classModel modelWithDict3:dic];
//                    [arrM addObject:model];
//                }
//
//                //把模型数组赋值给value
//                value = arrM;
//            }
//        }
//        //如果模型属性数量大于字典键值对数量，模型属性会被赋值为nil而报错
//        if (value) {
//            //给模型中属性赋值
//            [objc setObject:value forKey:key];
//        }
//    }
//    return objc;
//}



@end





